package com.syf.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.syf.domain.AccountDetail;
import com.syf.domain.Coin;
import com.syf.domain.Config;
import com.syf.dto.MarketDto;
import com.syf.feign.MarketServiceFeign;
import com.syf.mappers.AccountVoMappers;
import com.syf.service.AccountDetailService;
import com.syf.service.CoinService;
import com.syf.service.ConfigService;
import com.syf.vo.AccountVo;
import com.syf.vo.SymbolAssetVo;
import com.syf.vo.UserTotalAccountVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.syf.mapper.AccountMapper;
import com.syf.domain.Account;
import com.syf.service.AccountService;
import org.springframework.util.CollectionUtils;

/**
*
*
*@author 苏耀奋
*@create 2021-10-12 18:48
*/
@Service
@Slf4j
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService{

    @Autowired
    private CoinService coinService; //币种

    @Autowired
    private ConfigService configService; //配置

    @Autowired
    private AccountDetailService accountDetailService; //账户详细

    @Autowired
    private MarketServiceFeign marketServiceFeign; //市场

    /**
     * 1.充值成功后，给用户资金的划转
     * @param adminId
     * @param userId
     * @param coinId
     * @param num
     * @param fee
     * @return
     */
    @Override
    public Boolean transferAccountAmount(Long adminId, Long userId, Long coinId, Long orderNum, BigDecimal num, BigDecimal fee, String remark, String businessType, Byte direction) {
        Account coinAccount = getCoinAccount(coinId, userId);   //获取用户的某种币的资产
        if (coinAccount == null) {
            throw new IllegalArgumentException("用户当前的该币种的余额不存在");
        }
        // 增加一条流水记录
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setCoinId(coinId);
        accountDetail.setUserId(userId);
        accountDetail.setAmount(num);   //入账、出账的 币种数量
        accountDetail.setFee(fee);
        accountDetail.setOrderId(orderNum);
        accountDetail.setAccountId(coinAccount.getId());
        accountDetail.setRefAccountId(coinAccount.getId());
        accountDetail.setRemark(remark);
        accountDetail.setBusinessType(businessType);
        accountDetail.setDirection(direction);
        accountDetail.setCreated(new Date());
        boolean save = accountDetailService.save(accountDetail);

        if (save) { // 用户余额的增加
            coinAccount.setBalanceAmount(coinAccount.getBalanceAmount().add(num));  //总资产的 币种数量
            boolean updateById = updateById(coinAccount);
            return updateById;
        }
        return save;
    }

    /**
     * 2.提现成功后，给用户账户 扣减钱
     *
     * @param adminId      操作的人
     * @param userId       用户的id
     * @param coinId       币种的id
     * @param orderNum     订单的编号
     * @param num          扣减的余额
     * @param fee          费用
     * @param remark       备注
     * @param businessType 业务类型
     * @param direction    方向
     * @return
     */
    @Override
    public Boolean decreaseAccountAmount(Long adminId, Long userId, Long coinId, Long orderNum, BigDecimal num, BigDecimal fee, String remark, String businessType, byte direction) {
        Account coinAccount = getCoinAccount(coinId, userId);
        if (coinAccount == null) {
            throw new IllegalArgumentException("账户不存在");
        }
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setUserId(userId); //用户id
        accountDetail.setCoinId(coinId); //币种id
        accountDetail.setAmount(num); //资产(币种)数量
        accountDetail.setFee(fee); //手续费
        accountDetail.setOrderId(orderNum); //订单ID
        accountDetail.setAccountId(coinAccount.getId()); //账户ID
        accountDetail.setRefAccountId(coinAccount.getId());  //该笔流水资金关联方的账户id
        accountDetail.setRemark(remark);
        accountDetail.setBusinessType(businessType);  //业务类型
        accountDetail.setDirection(direction); //入账为1，出账为2
        //创建时间(自动创建)、ID(自增)
        boolean save = accountDetailService.save(accountDetail);
        if (save) { // 新增了流水记录
            BigDecimal balanceAmount = coinAccount.getBalanceAmount();  //币种可用金额
            BigDecimal result = balanceAmount.subtract(num);  //最后总资产 = 原可用资产(币种数量) - 要提现的数量
            //先减，再判断是否 > 0  或者 先判断大小 再减都行
            if (result.compareTo(BigDecimal.ONE) > 0) {
                coinAccount.setBalanceAmount(result);  //币种可用金额
                coinAccount.setFreezeAmount(coinAccount.getFreezeAmount().add(num));  //币种冻结金额
                return updateById(coinAccount);
            } else {
                throw new IllegalArgumentException("余额不足");
            }
        }
        return false;
    }


    /**
     * 3.获取用户的某种币的资产
     *
     * @param coinId
     * @param userId
     * @return
     */
    private Account getCoinAccount(Long coinId, Long userId) {

        return getOne(new LambdaQueryWrapper<Account>()
                .eq(Account::getCoinId, coinId)
                .eq(Account::getUserId, userId)
                .eq(Account::getStatus, 1)
        );
    }



    /**
     * 4.1 查询某个用户的货币资产【根据 查询用户的账号ID】
     *      币种名称 -> 币种对象 -> 币种ID + userId -> Account
     * @param userId   用户的id
     * @param coinName 货币的名称
     * @return
     */
    @Override
    public Account findByUserAndCoin(Long userId, String coinName) {   //参数不一样
        Coin coin = coinService.getCoinByCoinName(coinName);
        if (coin == null) {
            throw new IllegalArgumentException("货币不存在");
        }

        Account account = getOne(new LambdaQueryWrapper<Account>()
                .eq(Account::getUserId, userId)
                .eq(Account::getCoinId, coin.getId())
        );
        if (account == null) {
            throw new IllegalArgumentException("该资产不存在");
        }

        Config sellRateConfig = configService.getConfigByCode("USDT2CNY");
        account.setSellRate(new BigDecimal(sellRateConfig.getValue())); // 出售的费率

        Config setBuyRateConfig = configService.getConfigByCode("CNY2USDT");
        account.setBuyRate(new BigDecimal(setBuyRateConfig.getValue())); // 买进来的费率

        return account;
    }

    /**
     * 4.2.暂时锁定用户的资产 【GCN卖出，添加一个 提现记录，等待提现审核】
     *       可用币种数量 - 锁定的金额
     *       冻结的数量 + 锁定的金额
     *
     *  【还有取消委托单，则解锁用户资产】
     *
     * @param userId  用户的id
     * @param coinId  币种的id
     * @param mum     锁定的金额
     * @param type    资金流水的类型
     * @param orderId 订单的Id
     * @param fee
     */
    @Override
    public void lockUserAmount(Long userId, Long coinId, BigDecimal mum, String type, Long orderId, BigDecimal fee) {
        Account account = getOne(new LambdaQueryWrapper<Account>()
                .eq(Account::getUserId, userId)
                .eq(Account::getCoinId, coinId)
        );
        if (account == null) {
            throw new IllegalArgumentException("您输入的资产类型不存在");
        }
        BigDecimal balanceAmount = account.getBalanceAmount();  //币种可用金额（币种的数量）
        if (balanceAmount.compareTo(mum) < 0) { // 库存的操作
            throw new IllegalArgumentException("账号的资金不足");
        }
        account.setBalanceAmount(balanceAmount.subtract(mum));  //币种可用金额 - mum((银行卡到账金额))
        account.setFreezeAmount(account.getFreezeAmount().add(mum));  //币种冻结金额
        boolean updateById = updateById(account);
        if (updateById) {  // 增加流水记录
            AccountDetail accountDetail = new AccountDetail(
                    null,  //ID
                    userId, //用户ID
                    coinId, //币种ID
                    account.getId(), //账户id
                    //该笔流水资金关联方的账户id
                    account.getId(), // 如果该订单时邀请奖励,有我们的ref的account ,否则,值和account 是一样的
                    orderId, //订单ID
                    (byte) 2, //入账为1，出账为2
                    type, //业务类型
                    mum, //资产数量
                    fee, //手续费
                    "用户提现", //备注
                    null,
                    null,
                    null
            );
            accountDetailService.save(accountDetail);
        }
    }


    /**
     * 5.计算用户的总的资产:
     *      1.总账户金额CNY(人民币) = 基础币 * 汇率 【所有的币种(根据币种金额 和兑换基础币的兑换价格) -> 兑换成基础币】
     *      2.基础币金额 = 当前币种金额Coin(所有币种) * 兑换价格Pirce
     *      3.兑换价格 = 查询(基础币 兑换 当前币种) 的交易对，获取开盘价格(兑换价格)
     *      4.for: 所有币种 -> 获取 当前币种Coin
     *
     *      基础币 兑换 账户当前币种的 兑换价格（一个当前币 可以兑换 多少个基础币 ）
     *      基础币金额 = 当前币种金额 * 兑换价格
     *
     *      所有的币种中 也包括了基础币，当是基础币时，兑换价格为1
     *      市场Market(交易对)： 某币1 兑换 某币2的 兑换情况
     *
     *      Account -> 人民币、各种币种金额
     *
     * @param userId
     * @return
     */
    @Override
    public UserTotalAccountVo getUserTotalAccount(Long userId) {
        // 计算总资产
        UserTotalAccountVo userTotalAccountVo = new UserTotalAccountVo();
        BigDecimal basicCoin2CnyRate = BigDecimal.ONE; // 汇率(基础币 -> 账户金额的汇率)
        BigDecimal basicCoin = BigDecimal.ZERO; // 平台计算币的基础币金额 (初始化 0 )   由账户当前币 兑换 为基础币
        List<AccountVo> assertList = new ArrayList<AccountVo>();

        // 用户的总资产位于Account 里面【Account -> 人民币、各种币种金额】
        List<Account> accounts = list(new LambdaQueryWrapper<Account>()
                .eq(Account::getUserId, userId)
        );
        if (CollectionUtils.isEmpty(accounts)) { //初始化数据
            userTotalAccountVo.setAssertList(assertList);
            userTotalAccountVo.setAmountUs(BigDecimal.ZERO);
            userTotalAccountVo.setAmount(BigDecimal.ZERO);
            return userTotalAccountVo; //
        }

        // 获取所有的币种
        AccountVoMappers mappers = AccountVoMappers.INSTANCE;
        for (Account account : accounts) {
            AccountVo accountVo = mappers.toConvertVo(account);  //Account -》 AccountVo

            //1.获取账户的币种Coin
            Long coinId = account.getCoinId();
            Coin coin = coinService.getById(coinId);
            if (coin == null || coin.getStatus() != (byte) 1) {
                continue;
            }
            // 设置币的信息
            accountVo.setCoinName(coin.getName());
            accountVo.setCoinImgUrl(coin.getImg());
            accountVo.setCoinType(coin.getType());
            accountVo.setWithdrawFlag(coin.getWithdrawFlag());
            accountVo.setRechargeFlag(coin.getRechargeFlag());
            accountVo.setFeeRate(BigDecimal.valueOf(coin.getRate()));
            accountVo.setMinFeeNum(coin.getMinFeeNum());
            // 计算总的账面余额(币种可用金额 + 冻结金额)
            BigDecimal volume = accountVo.getBalanceAmount().add(accountVo.getFreezeAmount());
            accountVo.setCarryingAmount(volume); // 总的账面余额

            assertList.add(accountVo);

            // 一个当前币 可以兑换 多少个基础币: 将 当前币种coinId 和 我们系统统计币使用的基币basicCoinId 转化
            BigDecimal currentPrice = getCurrentCoinPrice(coinId); //2.基础币 兑换 当前币种的 兑换价格
            BigDecimal total = volume.multiply(currentPrice); //3.当前币种金额 * 兑换价格 = 基础币金额
            basicCoin = basicCoin.add(total); // 将该子资产添加到我们的总资产里面【基础币金额】
        }

        //4.账户金额CNY(人民币) = 基础币 * 汇率
        userTotalAccountVo.setAmount(basicCoin.multiply(basicCoin2CnyRate).setScale(8, RoundingMode.HALF_UP)); // 总的人民币
        userTotalAccountVo.setAmountUs(basicCoin); // 总的平台计算的币种(基础币)
        userTotalAccountVo.setAmountUsUnit("GCN");
        userTotalAccountVo.setAssertList(assertList);
        return userTotalAccountVo;
    }


    /**
     * 5.1 一个当前币 可以兑换 多少个基础币【基础币 -> 当前币 的兑换价格】:
     *      基础币 兑换 账户当前币种的 兑换价格  -》  当前币金额 * 价格 = 基础币金额
     * @param coinId
     * @return
     */
    private BigDecimal getCurrentCoinPrice(Long coinId) {
        // 1 查询我们的基础币是什么?
        Config configBasicCoin = configService.getConfigByCode("PLATFORM_COIN_ID"); // 基础币
        if (configBasicCoin == null) {
            throw new IllegalArgumentException("请配置基础币后使用");
        }
        Long basicCoinId = Long.valueOf(configBasicCoin.getValue());
        if (coinId.equals(basicCoinId)) { // 该币就是基础币，兑换价格为1
            return BigDecimal.ONE; //基础币 -> 基础币 = 1
        }

        //【核心】当前币不是基础币：我们需要查询交易市场Market(基础币 -> 当前币 的兑换价格，即)
        MarketDto market = marketServiceFeign.findByCoinId(basicCoinId, coinId); //buyCoinId, sellCoinId
        if (market != null) { // 存在交易对
            return market.getOpenPrice(); //开盘价(基础币 兑换 账户当前币种的 兑换价格)
        } else {
            log.error("不存在当前币和平台币兑换的市场,请后台人员及时添加");   // 该交易对不存在?
            return BigDecimal.ZERO;//TODO
        }
    }



    /**
     * 6.统计用户交易对的资产【根据币种查询各自卖出/买入的数据(余额、单位)】
     *
     * @param symbol 交易对的Symbol
     * @param userId 用户的Id
     * @return
     */
    @Override
    public SymbolAssetVo getSymbolAssert(String symbol, Long userId) {
        SymbolAssetVo symbolAssetVo = new SymbolAssetVo();
        /**
         * 远程调用获取市场
         */
        MarketDto marketDto = marketServiceFeign.findBySymbol(symbol);

        // 1.查询报价(买入)货币资产
        @NotNull Long buyCoinId = marketDto.getBuyCoinId(); // 报价货币的Id
        Account buyCoinAccount = getCoinAccount(buyCoinId, userId);
        symbolAssetVo.setBuyAmount(buyCoinAccount.getBalanceAmount());  //币种可用金额
        symbolAssetVo.setBuyLockAmount(buyCoinAccount.getFreezeAmount()); //币种冻结金额
        symbolAssetVo.setBuyFeeRate(marketDto.getFeeBuy());  //买入手续费率
        Coin buyCoin = coinService.getById(buyCoinId);
        symbolAssetVo.setBuyUnit(buyCoin.getName());  //买入币种名称

        //2.卖出币种
        @NotBlank Long sellCoinId = marketDto.getSellCoinId();
        Account coinAccount = getCoinAccount(sellCoinId, userId);
        symbolAssetVo.setSellAmount(coinAccount.getBalanceAmount());  //币种可用金额
        symbolAssetVo.setSellLockAmount(coinAccount.getFreezeAmount());//币种冻结金额
        symbolAssetVo.setSellFeeRate(marketDto.getFeeSell()); //卖出手续费率
        Coin sellCoin = coinService.getById(sellCoinId);
        symbolAssetVo.setSellUnit(sellCoin.getName());  //卖出币种名称

        return symbolAssetVo;
    }


    /**
     * 7. 经过撮合系统交易完成记录 -》 划转买入的账户余额
     *         两个方法不能写在一起【买单和卖单支付的币种不同】：
     *              A用户是买单记录----B是卖单记录
     *              B用户是买单记录----A是卖单记录
     * @param fromUserId
     * @param toUserId
     * @param coinId
     * @param amount
     * @param businessType
     * @param orderId
     */
    @Override
    public void transferBuyAmount(Long fromUserId, Long toUserId, Long coinId, BigDecimal amount, String businessType, Long orderId) {
        //1.获取 买单用户ID 某币种资产
        Account fromAccount = getCoinAccount(coinId, fromUserId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new IllegalArgumentException("资金账户异常");
        } else {
            //2.获取 卖单用户ID 某币种资产
            Account toAccount = getCoinAccount(coinId, toUserId);
            if (toAccount == null) {
                throw new IllegalArgumentException("资金账户异常");
            } else {
                //3.资产变动
                boolean count1 = decreaseAmount(fromAccount, amount);  //买单--扣减资产
                boolean count2 = addAmount(toAccount, amount);  //卖单--增加资产
                //4.资产划分成功，添加两条资产明细记录
                if (count1 && count2) {
                    List<AccountDetail> accountDetails = new ArrayList(2);
                    AccountDetail fromAccountDetail = new AccountDetail(fromUserId, coinId, fromAccount.getId(), toAccount.getId(), orderId, 2, businessType, amount, BigDecimal.ZERO, businessType);
                    AccountDetail toAccountDetail = new AccountDetail(toUserId, coinId, toAccount.getId(), fromAccount.getId(), orderId, 1, businessType, amount, BigDecimal.ZERO, businessType);
                    accountDetails.add(fromAccountDetail);
                    accountDetails.add(toAccountDetail);

                    accountDetails.addAll(accountDetails);
                } else {
                    throw new RuntimeException("资金划转失败");
                }
            }
        }
    }

    private boolean addAmount(Account account, BigDecimal amount) {
        account.setBalanceAmount(account.getBalanceAmount().add(amount));
        return updateById(account);
    }

    private boolean decreaseAmount(Account account, BigDecimal amount) {
        account.setBalanceAmount(account.getBalanceAmount().subtract(amount));
        return updateById(account);
    }

    /**
     * 8.经过撮合系统交易完成记录 -》 划转出售的成功的账户余额
     * @param fromUserId
     * @param toUserId
     * @param coinId
     * @param amount
     * @param businessType
     * @param orderId
     */
    @Override
    public void transferSellAmount(Long fromUserId, Long toUserId, Long coinId, BigDecimal amount, String businessType, Long orderId) {
        Account fromAccount = getCoinAccount(coinId, fromUserId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new IllegalArgumentException("资金账户异常");
        } else {
            Account toAccount = getCoinAccount(coinId, toUserId);
            if (toAccount == null) {
                throw new IllegalArgumentException("资金账户异常");
            } else {
                boolean count1 = addAmount(fromAccount, amount);
                boolean count2 = decreaseAmount(toAccount, amount);
                if (count1 && count2) {
                    List<AccountDetail> accountDetails = new ArrayList(2);
                    AccountDetail fromAccountDetail = new AccountDetail(fromUserId, coinId, fromAccount.getId(), toAccount.getId(), orderId, 2, businessType, amount, BigDecimal.ZERO, businessType);
                    AccountDetail toAccountDetail = new AccountDetail(toUserId, coinId, toAccount.getId(), fromAccount.getId(), orderId, 1, businessType, amount, BigDecimal.ZERO, businessType);
                    accountDetails.add(fromAccountDetail);
                    accountDetails.add(toAccountDetail);

                    accountDetails.addAll(accountDetails);
                } else {
                    throw new RuntimeException("资金划转失败");
                }
            }
        }
    }

}
